/**
 * @copyright (c) 2024, MacRsh
 *
 * @license SPDX-License-Identifier: Apache-2.0
 *
 * @date 2024-08-08    MacRsh       First version
 */

#include <atomic.h>

#if !defined(__CC_ARM) && !defined(__GNUC__) && !defined(__clang__)
/* This interface to be implemented by the user */
int irq_disable(void);
void irq_enable(int mask);
#endif /* !defined(__CC_ARM) && !defined(__GNUC__) && !defined(__clang__) */ 

/**
 * @brief This function initializes an atomic.
 *
 * @param atomic The atomic to initialize.
 * @param value The initial value of the atomic.
 */
void atomic_init(Atomic_t *atomic, Atomic_t value) {
    if (!atomic) {
        return;
    }

    /* Initialize the atomic */
    *atomic = value;
}

/**
 * @brief This function deinitializes an atomic.
 *
 * @param atomic The atomic to deinitialize.
 */
void atomic_deinit(Atomic_t *atomic) {
    if (!atomic) {
        return;
    }

    /* Deinitialize the atomic */
    *atomic = 0;
}

/**
 * @brief This function creates an atomic.
 *
 * @param value The initial value of the atomic.
 * @return The created atomic on success, otherwise NULL.
 */
Atomic_t *atomic_new(Atomic_t value) {
    Atomic_t *atomic;

    /* Create atomic */
    atomic = malloc(sizeof(Atomic_t));
    if (!atomic) {
        return NULL;
    }
    atomic_init(atomic, value);
    return atomic;
}

/**
 * @brief This function destroys an atomic.
 *
 * @param atomic The atomic to destroy.
 */
void atomic_del(Atomic_t *atomic) {
    if (!atomic) {
        return;
    }

    /* Destroy atomic */
    free(atomic);
}

/**
 * @brief This function loads an atomic.
 *
 * @param atomic The atomic to load.
 * @return The loaded atomic.
 */
Atomic_t atomic_load(Atomic_t *atomic) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* Load the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_load_n(atomic, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function stores an atomic.
 *
 * @param atomic The atomic to store.
 * @param value The value to store.
 */
void atomic_store(Atomic_t *atomic, Atomic_t value) {
    int mask;

    if (!atomic) {
        return;
    }

    /* Store the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    __atomic_store_n(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    *atomic = value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
}

/**
 * @brief This function exchanges an atomic.
 *
 * @param atomic The atomic to exchange.
 * @param value The value to exchange.
 * @return The exchanged atomic.
 */
Atomic_t atomic_exchange(Atomic_t *atomic, Atomic_t value) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* Exchange the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_exchange_n(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    *atomic = value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function fetches and adds an atomic.
 *
 * @param atomic The atomic to add.
 * @param value The value to add.
 * @return The fetched atomic.
 */
Atomic_t atomic_fetch_add(Atomic_t *atomic, Atomic_t value) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* Add the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_fetch_add(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    *atomic += value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function fetches and subtracts an atomic.
 *
 * @param atomic The atomic to subtract.
 * @param value The value to subtract.
 * @return The fetched atomic.
 */
Atomic_t atomic_fetch_sub(Atomic_t *atomic, Atomic_t value) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* Subtract the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_fetch_sub(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    *atomic -= value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function fetches and ands an atomic.
 * 
 * @param atomic The atomic to and.
 * @param value The value to and.
 * @return The fetched atomic.
 */
Atomic_t atomic_fetch_and(Atomic_t *atomic, Atomic_t value) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* And the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_fetch_and(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    *atomic &= value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function fetches and ors an atomic.
 * 
 * @param atomic The atomic to or.
 * @param value The value to or.
 * @return The fetched atomic.
 */
Atomic_t atomic_fetch_or(Atomic_t *atomic, Atomic_t value) {
    Atomic_t tmp;
    int mask;

    if (!atomic) {
        tmp = 0;
        return tmp;
    }

    /* Or the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    tmp = __atomic_fetch_or(atomic, value, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    tmp = *atomic;
    *atomic |= value;
    irq_enable(mask);
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
    return tmp;
}

/**
 * @brief This function compares and exchanges an atomic.
 *
 * @param atomic The atomic to compare and exchange.
 * @param old_value The old value to compare.
 * @param new_value The new value to exchange.
 * @return True if the compare and exchange was successful, otherwise false.
 */
bool atomic_compare_exchange(Atomic_t *atomic, Atomic_t old_value,
                             Atomic_t new_value) {
    int mask;

    if (!atomic) {
        return false;
    }

    /* Compare and exchange the atomic */
#if defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__)
    (void)mask;
    return __atomic_compare_exchange_n(atomic, &old_value, new_value, false,
                                       __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#else
    mask = irq_disable();
    if (*atomic != old_value) {
        irq_enable(mask);
        return false;
    }
    *atomic = new_value;
    irq_enable(mask);
    return true;
#endif /* defined(__CC_ARM) || defined(__GNUC__) || defined(__clang__) */
}
